﻿using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Media;

namespace Black.SurfaceToolkit
{
    /// <summary>
    /// Fournit des méthodes d'aide à la manipulation des arbres visuels et logiques.
    /// </summary>
    public static class TreesHelper
    {
        #region Fonctions statiques publiques

        /// <summary>
        /// Obtient la profondeur de l'élément spécifié dans l'arbre logique.
        /// </summary>
        /// <param name="p_oElement">Elément dont on veut la profondeur dans l'arbre logique.</param>
        /// <returns>Profondeur de <paramref name="p_oElement" /> dans l'arbre logique.</returns>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="p_oElement" /> est <see langword="null" />.</exception>
        public static int GetLogicalTreeDepth(this DependencyObject p_oElement)
        {
            if (p_oElement == null)
                throw new ArgumentNullException("p_oElement");

            int iResult = -1;

            do
            {
                p_oElement = LogicalTreeHelper.GetParent(p_oElement);
                iResult++;
            }
            while (p_oElement != null);

            return (iResult);
        }

        /// <summary>
        /// Obtient la position de l'élément par rapport à son parent direct dans l'arbre logique.
        /// </summary>
        /// <param name="p_oElement">Elément dont on veut la position par rapport à son parent direct dans l'arbre logique.</param>
        /// <returns>Position de <paramref name="p_oElement" /> par rapport à son parent direct dans l'arbre logique.</returns>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="p_oElement" /> est <see langword="null" />.</exception>
        public static int GetLogicalTreeChildIndex(this DependencyObject p_oElement)
        {
            if (p_oElement == null)
                throw new ArgumentNullException("p_oElement");

            DependencyObject oParent = LogicalTreeHelper.GetParent(p_oElement);

            if (oParent == null)
                return (0);

            int iResult = 0;

            foreach (DependencyObject oChild in LogicalTreeHelper.GetChildren(oParent))
            {
                if (object.ReferenceEquals(oChild, p_oElement))
                    break;

                iResult++;
            }

            return (iResult);
        }

        /// <summary>
        /// Obtient la transformation à la racine de l'élément spécifié.
        /// </summary>
        /// <param name="p_oElement">Elément dont on veut la transformation à la racine.</param>
        /// <returns>Transformation à la racine de <paramref name="p_oElement" />.</returns>
        public static Transform GetRootTransform(this UIElement p_oElement)
        {
            PresentationSource oPresentationSource = PresentationSource.FromVisual(p_oElement);

            if (oPresentationSource == null)
                return (Transform.Identity);

            return (p_oElement.TransformToAncestor(oPresentationSource.RootVisual) as Transform);
        }

        /// <summary>
        /// Obtient les éléments visuels en contact avec l'élément et le paramètre spécifiés.
        /// </summary>
        /// <param name="p_oReference">Base de la recherche.</param>
        /// <param name="p_oHitTestParameter">Paramètre de recherche.</param>
        /// <param name="p_oFilter">Filtre complémentaire de la recherche.</param>
        /// <returns>Eléments visuels au point spécifié.</returns>
        /// <typeparam name="T">Type de l'objet à obtenir.</typeparam>
        public static IEnumerable<KeyValuePair<T, HitTestResult>> GetVisualsUnder<T>(this Visual p_oReference, HitTestParameters p_oHitTestParameter, Predicate<DependencyObject> p_oFilter) where T : DependencyObject
        {
            List<HitTestResult> oHitTestResults = new List<HitTestResult>();
            VisualTreeHelper.HitTest(p_oReference, new HitTestFilterCallback(p_oTarget =>
            {
                if (p_oFilter(p_oTarget) && (bool)p_oTarget.GetValue(UIElement.IsHitTestVisibleProperty))
                    return (HitTestFilterBehavior.Continue);

                return (HitTestFilterBehavior.ContinueSkipSelfAndChildren);
            }), new HitTestResultCallback(p_oResult =>
            {
                oHitTestResults.Add(p_oResult);
                return (HitTestResultBehavior.Continue);
            }), p_oHitTestParameter);

            foreach (HitTestResult oResult in oHitTestResults)
            {
                DependencyObject oParent = oResult.VisualHit;
                T oTParent = oParent as T;
                while (oTParent == null && oParent != null)
                {
                    oParent = VisualTreeHelper.GetParent(oParent);
                    oTParent = oParent as T;
                }

                yield return (new KeyValuePair<T, HitTestResult>(oTParent, oResult));
            }
        }

        /// <summary>
        /// Obtient la profondeur de l'élément spécifié dans l'arbre visuel.
        /// </summary>
        /// <param name="p_oElement">Elément dont on veut la profondeur dans l'arbre visuel.</param>
        /// <returns>Profondeur de <paramref name="p_oElement" /> dans l'arbre visuel.</returns>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="p_oElement" /> est <see langword="null" />.</exception>
        public static int GetVisualTreeDepth(this DependencyObject p_oElement)
        {
            if (p_oElement == null)
                throw new ArgumentNullException("p_oElement");

            int iResult = -1;

            do
            {
                p_oElement = VisualTreeHelper.GetParent(p_oElement);
                iResult++;
            }
            while (p_oElement != null);

            return (iResult);
        }

        /// <summary>
        /// Obtient la position de l'élément par rapport à son parent direct dans l'arbre visuel.
        /// </summary>
        /// <param name="p_oElement">Elément dont on veut la position par rapport à son parent direct dans l'arbre visuel.</param>
        /// <returns>Position de <paramref name="p_oElement" /> par rapport à son parent direct dans l'arbre visuel.</returns>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="p_oElement" /> est <see langword="null" />.</exception>
        public static int GetVisualTreeChildIndex(this DependencyObject p_oElement)
        {
            if (p_oElement == null)
                throw new ArgumentNullException("p_oElement");

            DependencyObject oParent = VisualTreeHelper.GetParent(p_oElement);

            if (oParent == null)
                return (0);

            int iCountChildren = VisualTreeHelper.GetChildrenCount(oParent);

            for (int i = 0; i < iCountChildren; i++)
            {
                if (object.ReferenceEquals(VisualTreeHelper.GetChild(oParent, i), p_oElement))
                    return (i);
            }

            return (0);
        }

        /// <summary>
        /// Obtient une valeur indiquant si l'élément spécifié est l'enfant logique de l'élément de base.
        /// </summary>
        /// <param name="p_oElement">Elément de base.</param>
        /// <param name="p_oChildElement">Elément dont on veut savoir s'il est l'enfant logique de <paramref name="p_oElement" />.</param>
        /// <returns><see langword="true" /> si <paramref name="p_oChildElement" /> est l'enfant logique de <paramref name="p_oElement" />, <see langword="false" /> sinon.</returns>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="p_oElement" /> est <see langword="null" />.</exception>
        public static bool ContainsLogicalChild(this DependencyObject p_oElement, DependencyObject p_oChildElement)
        {
            if (p_oElement == null)
                throw new ArgumentNullException("p_oElement");

            while (p_oChildElement != null)
            {
                if (p_oChildElement == p_oElement)
                    return (true);

                p_oChildElement = LogicalTreeHelper.GetParent(p_oChildElement);
            }

            return (false);
        }

        /// <summary>
        /// Obtient une valeur indiquant si l'élément spécifié est l'enfant visuel de l'élément de base.
        /// </summary>
        /// <param name="p_oElement">Elément de base.</param>
        /// <param name="p_oChildElement">Elément dont on veut savoir s'il est l'enfant visuel de <paramref name="p_oElement" />.</param>
        /// <returns><see langword="true" /> si <paramref name="p_oChildElement" /> est l'enfant visuel de <paramref name="p_oElement" />, <see langword="false" /> sinon.</returns>
        /// <exception cref="T:System.ArgumentNullException"><paramref name="p_oElement" /> est <see langword="null" />.</exception>
        public static bool ContainsVisualChild(this DependencyObject p_oElement, DependencyObject p_oChildElement)
        {
            if (p_oElement == null)
                throw new ArgumentNullException("p_oElement");

            while (p_oChildElement != null)
            {
                if (p_oChildElement == p_oElement)
                    return (true);

                p_oChildElement = VisualTreeHelper.GetParent(p_oChildElement);
            }

            return (false);
        }

        #endregion
    }
}
